﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SqlSchemaReader.cs" company="MyCoreLib">
//   Copyright (C) 2014 Alexey Evlampiev. All rights reserved.
// </copyright>
// <summary>
//   Defines the SqlSchemaReader type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System.Collections.Generic;

namespace MyCoreLib.Automation.Data.SqlClient.Metadata
{
    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    public abstract class SqlSchemaReader : ISqlSchemaReader
    {
        public readonly string ConnectionString;
        private readonly IOutput Output;

        protected SqlSchemaReader(IOutput output, string connectionString)
        {
            if (String.IsNullOrWhiteSpace(connectionString))
            {
                throw new ArgumentException("connectionString");
            }
            ConnectionString = connectionString;
            Output = output;           
        }

        public abstract Task<string[]> LoadStoredProcedureFullNamesAsync(System.Threading.CancellationToken token);

        public abstract Task<string[]> LoadUserTableTypeFullNamesAsync(System.Threading.CancellationToken token);

        public abstract Task<SqlClrTypeMetadataCollection> LoadSqlClrTypeMetadataAsyn(CancellationToken token);

        public abstract Task<SqlUserTableTypeMetadataCollection> LoadSqlUserTableTypeMetadataAsyn(CancellationToken token);

        public async Task<SqlStoredProcedureMetadata> LoadStoredProcedureMetadataAsync(
            string spFullName, 
            Task<SqlClrTypeMetadataCollection> loadSqlClrMetadataTask,
            Task<SqlUserTableTypeMetadataCollection> loadUserTableTypeMetadataTask,
            CancellationToken token)
        {
            using (var connection = new SqlConnection(ConnectionString))
            using (var command = new SqlCommand(spFullName, connection) { CommandType = CommandType.StoredProcedure })
            {
                await connection.OpenAsync(token);
                SqlCommandBuilder.DeriveParameters(command);
                var adapter = new SqlDataAdapter(command);
                var dataSet = new DataSet(spFullName);
                try
                {
                    foreach (SqlParameter parameter in command.Parameters)
                    {
                        if (parameter.SqlDbType == SqlDbType.Udt)
                        {
                            var info = await loadSqlClrMetadataTask;
                            var metadata = info.FindByName(parameter.UdtTypeName);
                            parameter.UdtTypeName = metadata.FullName;
                        }
                        else if (parameter.SqlDbType == SqlDbType.Structured)
                        {
                            var userTableTypeMetadataCollection = await loadUserTableTypeMetadataTask;
                            var metadata = userTableTypeMetadataCollection.FindByName(parameter.TypeName);
                            parameter.TypeName = metadata.FullName;
                            parameter.Value = metadata.CreateInstance();
                        }
                    }
                    adapter.FillSchema(dataSet, SchemaType.Source);
                }
                catch (SqlException ex)
                {
                    Output.Error("{0}: {1}", spFullName, ex.Message);                    
                }

                return new SqlStoredProcedureMetadata(spFullName, command.Parameters.Cast<SqlParameter>(), dataSet.Tables.Cast<DataTable>());
            }            
        }



        public async Task<SqlStoredProcedureMetadataCollection> LoadSelectedStoredProcedureMetadataAsync(
            IEnumerable<string> selectedProcedureFullNames, 
            Task<string[]> loadStoredProcedureFullNamesTask, 
            Task<SqlClrTypeMetadataCollection> loadSqlClrMetadataTask, 
            Task<SqlUserTableTypeMetadataCollection> loadSqlUserTableTypeMetadataTask, 
            CancellationToken token)
        {
            var databaseSpFullNames = await loadStoredProcedureFullNamesTask;
            var tasks = new List<Task<SqlStoredProcedureMetadata>>();
            foreach (var spFullName in selectedProcedureFullNames)
            {
                if (databaseSpFullNames.Contains(spFullName))
                {
                    tasks.Add(LoadStoredProcedureMetadataAsync(
                        spFullName,
                        loadSqlClrMetadataTask,
                        loadSqlUserTableTypeMetadataTask,
                        token));
                }
                else
                {
                    Output.Error("WTF");
                }
            }

            await Task.WhenAll(tasks);
            var collection = new SqlStoredProcedureMetadataCollection();
            foreach (var task in tasks)
            {
                collection.Add(task.Result);
            }
            return collection;
        }
    }
}
